using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace Woz
{
    public class ScreenThumbnailSlider : UserControl
    {
        /// <summary>
        /// default constructor
        /// </summary>
        public ScreenThumbnailSlider()
        {
            // set the padding
            this.Padding = new Padding(5);

            // set the starting position of the screen thumbnails
            this.mScreenThumbnailStartingTopPosition = 7;

            this.BackColor = Color.Gainsboro;
            this.BorderStyle = BorderStyle.None;

            // save the caret
            this.mCaret = Woz.Properties.resources.arrow;

            // create the list of screen thumbnails
            this.mScreenThumbnails = new List<Woz.ScreenThumbnail>();

            // intialize the slider container
            this.InitializeSliderContainer();

            // intialize the toolstrip container
            this.InitializeToolstripContainer();

            // register the callbacks
            this.RegisterCallbacks();
        }

        /// <summary>
        /// gets or sets the currently selected thumbnail in this screen thumbnail slider
        /// </summary>
        public Woz.ScreenThumbnail SelectedScreenThumbnail
        {
            get
            {
                return this.mSelectedScreenThumbnail;
            }

            set
            {
                // check if this entity is already selected
                if (value == this.mSelectedScreenThumbnail)
                {
                    // we dont want to reselect this thumbnail so just leave
                    return;
                }

                // then tell the screen manager to select this new screen
                Woz.ScreenManager.Instance.SelectedScreen = value.Screen;
            }
        }

        /// <summary>
        /// initialize the slider with all the screens currently in the screen manager
        /// </summary>
        public void RetrieveScreensFromScreenManager()
        {
            //foreach
            // add all of the screens that are not hidden to the slider
            foreach (Woz.Screen screen in Woz.ScreenManager.Instance.Screens)
            {
                // skip over hidden screens
                if (screen.Hidden)
                {
                    continue;
                }

                // this screen is not hidden to add a screen thumbnail for it
                this.AddScreenThumbnail(screen.SliderThumbnail, false);
            }

            // select the first screen
            this.mSelectedScreenThumbnail = this.mScreenThumbnails[0];
            this.mSelectedScreenThumbnail.Selected = true;

            // redraw the thumbnail for the first time
            this.RedrawThumbnails();
        }

        /// <summary>
        /// resets the thumbnail slider back to its initial state
        /// </summary>
        public void Reset()
        {
            // clear the ui
            this.mSliderContainer.Controls.Clear();

            // clear the list of screen thumbnails
            this.mScreenThumbnails.Clear();

            // clear the selected screen thumbnail
            this.mSelectedScreenThumbnail = null;
        }

        /// <summary>
        /// creates a new screen in the screen manager as well as a new thumbnail for our slider then selects it
        /// </summary>
        /// <returns>newly created screen thumbnail</returns>
        public Woz.ScreenThumbnail AddScreenThumbnail(Woz.ScreenThumbnail pScreenThumbnail)
        {
            return this.AddScreenThumbnail(pScreenThumbnail, true);
        }

        /// <summary>
        /// removes the selected thumbnail from our slider along with the associated screen from the screen manager
        /// </summary>
        public void RemoveScreenThumbnail(Woz.ScreenThumbnail pScreenThumbnail)
        {
            // for now block this removal if it is the last one
            // in the future maybe nuke this screen and add a new blank one
            if (Woz.ScreenManager.Instance.Screens.Count == 1)
            {
                return;
            }

            // unsubscribe from this thumbnails mousedown and mousemove and mouseup events
            pScreenThumbnail.MouseDown -= this.cScreenThumbnailMouseDown;
            pScreenThumbnail.MouseMove -= this.cScreenThumbnailMouseMove;
            pScreenThumbnail.MouseUp -= this.cScreenThumbnailMouseUp;

            // unsubscribe to this thumbnails rename event
            pScreenThumbnail.Renamed -= this.cScreenThumbnailRenamed;

            // set the selected screen in the screen manager to the screen associated with this screen thumbnail
            Woz.ScreenManager.Instance.SelectedScreen = pScreenThumbnail.Screen;

            // remove this screen from the screen manager
            Woz.ScreenManager.Instance.DeleteSelectedScreen();

            // remove the screen thumbnail from the slider panel
            this.mSliderContainer.Controls.Remove(pScreenThumbnail);

            // remove the screen thumbnail from the list of screen thumbnails
            int index = this.mScreenThumbnails.IndexOf(pScreenThumbnail);
            this.mScreenThumbnails.Remove(pScreenThumbnail);

            // this should never happen but we will deal with it anyways
            if (index < 0)
            {
                this.SelectedScreenThumbnail = this.mScreenThumbnails[0];
            }
            // if the screen we just removed was the last screen then we select the second to last screen
            else if (index >= this.mScreenThumbnails.Count)
            {
                this.SelectedScreenThumbnail = this.mScreenThumbnails[this.mScreenThumbnails.Count - 1];
            }
            // select the next screen
            else
            {
                this.SelectedScreenThumbnail = this.mScreenThumbnails[index];
            }

            // redraw the slider
            this.RedrawThumbnails();
        }

        /// <summary>
        /// clones the currently selected screen thumbnail in the slider 
        /// then selects it and also clones the screen in the screen manager
        /// </summary>
        /// <returns>newly cloned screen thumbnail</returns>
        public Woz.ScreenThumbnail CloneScreenThumbnail()
        {
            // clone this screen in the screen manager
            Woz.Screen screen = Woz.ScreenManager.Instance.CloneScreen(this.mSelectedScreenThumbnail.Screen);
            
            // merely call our add method with this screen
            return this.AddScreenThumbnail(screen.SliderThumbnail);
        }
        
        /// <summary>
        /// initializes the slider container
        /// </summary>
        private void InitializeSliderContainer()
        {
            // create the slider container
            this.mSliderContainer = new Panel();
            this.mSliderContainer.Margin = new Padding(0);
            this.mSliderContainer.Padding = new Padding(0);
            this.mSliderContainer.BorderStyle = BorderStyle.FixedSingle;
            this.mSliderContainer.BackColor = Color.WhiteSmoke;
            this.mSliderContainer.Location = new Point(0, 0);
            this.mSliderContainer.Size = new Size(this.Size.Width, this.Size.Height - Woz.InterfaceManager.Instance.ToolStrips["screen"].Height - 2);
            this.mSliderContainer.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

            // add the split container to the thumbnail slider
            this.Controls.Add(this.mSliderContainer);
        }

        /// <summary>
        /// initializes the toolstrip container
        /// </summary>
        private void InitializeToolstripContainer()
        {
            // get the toolstrip
            ToolStrip toolStrip = Woz.InterfaceManager.Instance.ToolStrips["screen"];

            // create the toolstrip container
            this.mToolstripContainer = new Panel();
            this.mToolstripContainer.Margin = new Padding(0);
            this.mToolstripContainer.Padding = new Padding(0);
            this.mToolstripContainer.BorderStyle = BorderStyle.None;
            this.mToolstripContainer.BackColor = Color.WhiteSmoke;
            this.mToolstripContainer.Location = new Point(0, this.Size.Height - toolStrip.Height);
            this.mToolstripContainer.Size = new Size(this.Size.Width, toolStrip.Height);
            this.mToolstripContainer.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;

            // add the tool strip to the toolstrip container
            toolStrip.GripStyle = ToolStripGripStyle.Hidden;
            toolStrip.Visible = true;
            this.mToolstripContainer.Controls.Add(toolStrip);

            // add the split container to the thumbnail slider
            this.Controls.Add(this.mToolstripContainer);
        }

        /// <summary>
        /// register all the callbacks
        /// </summary>
        private void RegisterCallbacks()
        {
            // subscribe to our buttons in the toolstrip
            Woz.InterfaceManager.Instance.Buttons["Add New Screen"].Click += new Button.ButtonHandler(this.cAddNewScreenButtonClick);
            Woz.InterfaceManager.Instance.Buttons["Remove Current Screen"].Click += new Button.ButtonHandler(this.cRemoveSelectedScreenButtonClick);
            Woz.InterfaceManager.Instance.Buttons["Clone Current Screen"].Click += new Button.ButtonHandler(this.cCloneSelectedScreenButtonClick);

            // subscribe to the relevant events in the screen manager
            Woz.ScreenManager.Instance.ScreenSelected += new ScreenManager.ScreenHandler(this.cScreenManagerHasScreenSelected);
        }

        /// <summary>
        /// redraw the thumbnails
        /// </summary>
        private void RedrawThumbnails()
        {
            // set the autoscroll min size
            if (this.mScreenThumbnails.Count > 0)
            {
                this.mSliderContainer.AutoScrollMinSize = new Size(this.Padding.Horizontal + this.mScreenThumbnails[0].Width,
                        this.mScreenThumbnails.Count * (this.Padding.Vertical + this.mScreenThumbnails[0].Height));
            }

            // get the size difference of the slider container
            int sizeDifference = this.mSliderContainer.Size.Width - this.mSliderContainer.ClientSize.Width;
            
            // rearrange all of the controls
            for (int i = this.mScreenThumbnails.Count - 1; i >= 0; i--)
            {
                this.mScreenThumbnails[i].TabIndex = i;
                this.mScreenThumbnails[i].Location = new Point(Math.Max(this.Padding.Left, ((this.Width - sizeDifference) / 2) - (Woz.ScreenThumbnail.ScreenThumbnailSize.Width / 2)),
                    (i * (Woz.ScreenThumbnail.ScreenThumbnailSize.Height + this.Padding.Vertical)) + this.Padding.Top + this.mSliderContainer.AutoScrollPosition.Y + this.mScreenThumbnailStartingTopPosition);
            }
        }

        /// <summary>
        /// creates a new screen in the screen manager as well as a new thumbnail for our slider then selects it
        /// </summary>
        /// <param name="pScreenThumbnail">the screen thumbnail to add</param>
        /// <param name="pRedraw">whether or not to redraw</param>
        /// <returns>newly created screen thumbnail</returns>
        private Woz.ScreenThumbnail AddScreenThumbnail(ScreenThumbnail pScreenThumbnail, bool pRedraw)
        {
            // check if the passed in screen was empty
            if (pScreenThumbnail == null)
            {
                // add a new screen to the screen manager
                Woz.Screen screen = Woz.ScreenManager.Instance.AddNewScreen("", false);
                pScreenThumbnail = screen.SliderThumbnail;
            }
            else
            {
                // merely add it to the screen manager
                Woz.ScreenManager.Instance.AddExistingScreen(pScreenThumbnail.Screen);
            }

            // subscribe to this thumbnails mousedown and mousemove and mouseup events
            pScreenThumbnail.MouseDown += this.cScreenThumbnailMouseDown;
            pScreenThumbnail.MouseMove += this.cScreenThumbnailMouseMove;
            pScreenThumbnail.MouseUp += this.cScreenThumbnailMouseUp;

            // subscribe to this thumbnails rename event
            pScreenThumbnail.Renamed += this.cScreenThumbnailRenamed;

            // hide the screen thumbnail
            pScreenThumbnail.Visible = false;

            // take the margins off of the control we are about to add
            //pScreenThumbnail.Margin = new Padding(0);

            // find the position to insert this screen thumbnail
            int index = this.mScreenThumbnails.IndexOf(this.mSelectedScreenThumbnail);
            
            // insert it into the screen thumbnails list
            if (index != -1)
            {
                this.mScreenThumbnails.Insert(index + 1, pScreenThumbnail);
            }
            else
            {
                this.mScreenThumbnails.Add(pScreenThumbnail);
            }

            // add it to the top panel
            this.mSliderContainer.Controls.Add(pScreenThumbnail);

            // redraw the slider
            if (pRedraw)
            {
                this.RedrawThumbnails();
            }

            // show the screen thumbnail
            pScreenThumbnail.Visible = true;

            // now reset the selected screen
            this.SelectedScreenThumbnail = pScreenThumbnail;

            // send this back because we may have instantiated this newly at the top of the function
            return pScreenThumbnail;
        }

        /// <summary>
        /// redraw the thumbnails when the control is resized
        /// </summary>
        /// <param name="pEventArgs">the event args</param>
        protected override void OnResize(EventArgs pEventArgs)
        {
            this.RedrawThumbnails();
            base.OnResize(pEventArgs);
        }

        /// <summary>
        /// renames the screen thumbnail
        /// </summary>
        /// <param name="pSender">the screen thumbnai that fired the rename</param>
        /// <param name="pNewName">the new name of the screen thumbnail</param>
        private void cScreenThumbnailRenamed(object pSender, string pNewName)
        {
            // create a rename action
            Woz.Action action = Woz.ActionManager.Instance.CreateAction(
                typeof(Woz.RenameScreenThumbnailAction), 
                pSender as Woz.ScreenThumbnail, 
                pNewName);

            // then execute it
            action.Execute();

            // scroll the selected screen into view
            this.mSliderContainer.ScrollControlIntoView(this.mSelectedScreenThumbnail);
        }

        /// <summary>
        /// create an add new screen thumbnail action in the action manager then executes it
        /// </summary>
        /// <param name="pButton">the button that was clicked</param>
        private void cAddNewScreenButtonClick(Woz.Button pButton)
        {
            // create an add new screen thumbnail action in the action manager
            Woz.Action action = Woz.ActionManager.Instance.CreateAction(typeof(Woz.AddNewScreenThumbnailAction), this);

            // then execute it
            action.Execute();

            // scroll the selected screen into view
            this.mSliderContainer.ScrollControlIntoView(this.mSelectedScreenThumbnail);
        }

        /// <summary>
        /// creates a remove screen thumbnail action in the action manager then executes it
        /// </summary>
        /// <param name="pButton">the button that was clicked</param>
        private void cRemoveSelectedScreenButtonClick(Woz.Button pButton)
        {
            // create a remove screen thumbnail action in the action manager
            Woz.Action action = Woz.ActionManager.Instance.CreateAction(typeof(Woz.RemoveScreenThumbnailAction), this);

            // then execute it
            action.Execute();

            // scroll the selected screen into view
            this.mSliderContainer.ScrollControlIntoView(this.mSelectedScreenThumbnail);
        }
        
        /// <summary>
        /// creates a clone screen thumbnail action in the action manager then executes it
        /// </summary>
        /// <param name="pButton">the button that was clicked</param>
        private void cCloneSelectedScreenButtonClick(Woz.Button pButton)
        {
            // create a clone screen thumbnail action in the action manager
            Woz.Action action = Woz.ActionManager.Instance.CreateAction(typeof(Woz.CloneScreenThumbnailAction), this);

            // then execute it
            action.Execute();

            // scroll the selected screen into view
            this.mSliderContainer.ScrollControlIntoView(this.mSelectedScreenThumbnail);
        }

        /// <summary>
        /// merely requests a selected screen update from the screen manager
        /// </summary>
        /// <param name="pSender">screen manager</param>
        /// <param name="pScreen">newly selected screen</param>
        private void cScreenManagerHasScreenSelected(object pSender, Woz.Screen pScreen)
        {
            // make sure this screen thumbnail exists in our list
            if (!this.mScreenThumbnails.Contains(pScreen.SliderThumbnail))
            {
                return;
            }

            // first tell the currently selected screen thumbnail to unselect itself
            if (this.mSelectedScreenThumbnail != null)
            {
                this.mSelectedScreenThumbnail.Selected = false;
            }

            // also set the currently selected screen thumbnail
            this.mSelectedScreenThumbnail = pScreen.SliderThumbnail;

            // then tell the screen thumbnail to look selected
            this.mSelectedScreenThumbnail.Selected = true;

            // update the display
            Woz.ScreenManager.Instance.RequestSelectedScreenUpdate(pScreen, Woz.InkSpaceConverter.Instance.InkSpaceCanvasRectangle);
        }

        /// <summary>
        /// find the screen thumbnail given a control by traversing the control's parent
        /// </summary>
        /// <param name="pControl">the control to start traversing</param>
        /// <returns>the parent screen thumbnail if found or null</returns>
        private Woz.ScreenThumbnail FindScreenThumbnail(Control pControl)
        {
            // try to find the parent of the control that is a screen thumbnail
            while (pControl != null)
            {
                // if this control is a screen thumbnail
                if (pControl is Woz.ScreenThumbnail)
                {
                    // return it as a screen thumbnail
                    return pControl as Woz.ScreenThumbnail;
                }

                // check if we've gone too far to save time
                else if (pControl is Woz.ScreenThumbnailSlider)
                {
                    return null;
                }

                // check the parent to see if it is a screen thumbnail
                pControl = pControl.Parent;
            }

            // this control does not contain a screen thumbnail so give up
            return null;
        }

        private Point FindParentLocation(Control pChild, Point pLocation)
        {
            while (pChild != null)
            {
                if (pChild == this)
                {
                    return pLocation;
                }
                pLocation.X += pChild.Left;
                pLocation.Y += pChild.Top;
                pChild = pChild.Parent;
            }
            return Point.Empty;
        }

        /// <summary>
        /// find the snap index given a point relative to the screen thumbnail slider
        /// </summary>
        /// <param name="pLocation">a point relative to the screen thumbnail slider</param>
        /// <returns>the snap index the location is nearest to or -1 if the location is not near a snap index</returns>
        private int FindSnapIndex(Point pLocation)
        {
            int topHalf = (Woz.ScreenThumbnail.ScreenThumbnailSize.Height / 7) * 3;
            int botHalf = (Woz.ScreenThumbnail.ScreenThumbnailSize.Height - topHalf);
            for (int i = 1; i < this.mScreenThumbnails.Count; i++)
            {
                if ((pLocation.Y > (this.mScreenThumbnails[i - 1].Top + botHalf)) &&
                    (pLocation.Y < (this.mScreenThumbnails[i].Top + topHalf)))
                {
                    return i;
                }
            }
            
            // check below first screen
            if ((pLocation.Y > 0) && (pLocation.Y < (this.mScreenThumbnails[0].Top + topHalf)))
            {
                return 0;
            }
            else if ((pLocation.Y > this.mScreenThumbnails[this.mScreenThumbnails.Count - 1].Top + botHalf) &&
                (pLocation.Y < this.mSliderContainer.ClientSize.Height))
            {
                return this.mScreenThumbnails.Count;
            }

            // check above last screen
            return -1;
        }

        private void cScreenThumbnailMouseDown(object pSender, EventArgs pEventArgs)
        {
            // find the screen thumbnail that we're about to move
            this.mLiveScreenThumbnail = this.FindScreenThumbnail(pSender as Control);
            
            // clear the snap index
            this.mSnapIndex = -1;

            // set the currently selected thumbnail to the live screen thumbnail
            if (this.mLiveScreenThumbnail != null)
            {
                this.SelectedScreenThumbnail = this.mLiveScreenThumbnail;
            }
        }

        private void cScreenThumbnailMouseUp(object pSender, MouseEventArgs pMouseEventArgs)
        {
            // we dont do nothing if there is no live screen thumbnail
            if (this.mLiveScreenThumbnail == null)
            {
                return;
            }

            // check the snap index against the live screens index
            if ((this.mSnapIndex != -1) && (this.mSnapIndex != this.mLiveScreenThumbnail.TabIndex))
            {
                // create the reorder action
                Woz.Action action = Woz.ActionManager.Instance.CreateAction(typeof(Woz.ReorderControlAction), 
                    this, 
                    this.mLiveScreenThumbnail, 
                    this.mSnapIndex);

                // and execute it
                action.Execute();
            }

            // clear the live screen thumbnail
            this.mLiveScreenThumbnail = null;

            // invalidate the thumbnail slider to force it to redraw
            this.mSliderContainer.Invalidate();
        }

        private void cScreenThumbnailMouseMove(object pSender, MouseEventArgs pMouseEventArgs)
        {
            // we dont do nothing if there is no live screen thumbnail
            if (this.mLiveScreenThumbnail == null)
            {
                return;
            }

            // make sure the left mouse button is being held down
            if (pMouseEventArgs.Button != MouseButtons.Left)
            {
                return;
            }

            // now draw the little snap index caret to signify that the user may move the control there
            using (Graphics sliderGraphics = this.mSliderContainer.CreateGraphics())
            {
                // find the slider location
                Point sliderLocation = this.FindParentLocation(pSender as Control, pMouseEventArgs.Location);

                // save the snap offset and the snap height
                int snapOffset = this.mSliderContainer.AutoScrollPosition.Y - ((this.Padding.Vertical - this.mCaret.Height) / 2) - this.Padding.Top + this.mScreenThumbnailStartingTopPosition;
                int snapHeight = Woz.ScreenThumbnail.ScreenThumbnailSize.Height + this.Padding.Vertical;
                int snapIndex = this.mSnapIndex;

                // find the snap index from the slider location
                this.mSnapIndex = this.FindSnapIndex(sliderLocation);
                this.mSnapIndex = ((this.mSnapIndex != this.mLiveScreenThumbnail.TabIndex) &&
                    (this.mSnapIndex != this.mLiveScreenThumbnail.TabIndex + 1)) ? this.mSnapIndex : -1;

                // check if there is a previous snap caret
                if ((snapIndex != -1) && (snapIndex != this.mSnapIndex))
                {
                    // clear it by drawing a rectangle with the same color as the background color over it
                    sliderGraphics.FillRectangle(Brushes.WhiteSmoke, this.mScreenThumbnails[0].Left - this.mCaret.Width,
                        (snapIndex * snapHeight) + snapOffset,
                        this.mCaret.Width, this.mCaret.Height);
                }

                // make sure the slider location is inside the viewable area
                if (!this.mSliderContainer.ClientRectangle.Contains(sliderLocation))
                {
                    return;
                }

                // make sure the snap index is a valid index and is not redundant
                if ((this.mSnapIndex != -1) && (snapIndex != this.mSnapIndex))
                {
                    sliderGraphics.DrawImage(this.mCaret,
                        this.mScreenThumbnails[0].Left - this.mCaret.Width,
                        (this.mSnapIndex * snapHeight) + snapOffset);
                }
            }
        }

        /// <summary>
        /// reorders the screen thumbnail slider by moving a screen thumbnail to a given index
        /// </summary>
        /// <param name="pThumbnail">the screen thumbnail to move</param>
        /// <param name="pNewIndex">the new index to move the screen thumbnail to</param>
        internal void ReorderScreenThumbnail(Woz.ScreenThumbnail pThumbnail, int pNewIndex)
        {
            // remove the screen thumbnail
            this.mScreenThumbnails.Remove(pThumbnail);

            // make sure the new index is not out of range
            if (pThumbnail.TabIndex < pNewIndex)
            {
                pNewIndex = Math.Max(0, pNewIndex - 1);
            }
            else if(pNewIndex > this.mScreenThumbnails.Count)
            {
                pNewIndex = this.mScreenThumbnails.Count;
            }

            // insert it at the new index
            this.mScreenThumbnails.Insert(pNewIndex, pThumbnail);
            
            // force all the thumbnails to be redrawn
            this.RedrawThumbnails();
        }
        
        /// <summary>
        /// index at which to insert a screen thumbnail when it gets dragged
        /// </summary>
        private int mSnapIndex;

        /// <summary>
        /// list of screen thumbnails
        /// </summary>
        private List<Woz.ScreenThumbnail> mScreenThumbnails;

        /// <summary>
        /// panel that contains the slider and the screen thumbnails
        /// </summary>
        private Panel mSliderContainer;

        /// <summary>
        /// panel that contains the tool strip for adding and removing and cloning screen thumbnails
        /// </summary>
        private Panel mToolstripContainer;

        /// <summary>
        /// image to show between controls when something is being dragged
        /// </summary>
        private Bitmap mCaret;

        /// <summary>
        /// save a reference to the selected screen thumbnail
        /// </summary>
        private Woz.ScreenThumbnail mSelectedScreenThumbnail;

        /// <summary>
        /// control which the user is currently trying to move
        /// </summary>
        private Woz.ScreenThumbnail mLiveScreenThumbnail;

        /// <summary>
        /// number of pixels to offset the beginning of the screen thumbnails
        /// </summary>
        private int mScreenThumbnailStartingTopPosition;
    }
}
